# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import functools
from collections import namedtuple

from coin.base.datetime_util import to_datetime, to_timestamp_int
from coin.exchange.base.order_gateway import OrderSide
from coin.exchange.base.tick import FixedTickPrice
from coin.strategy.mm.calculator import MovingSum
from coin.strategy.mm.simple_sim.executors import PassAggSimExecutor
from coin.strategy.mm.subscription import get_subscription_key

FEATURE_ROW_FIELDS = ['time', 'time_hr', 'product', 'kimchi']
_FeatureRow = namedtuple('FeatureRow', FEATURE_ROW_FIELDS)


class FeatureRow(_FeatureRow):
  @staticmethod
  def get_csv_header():
    return 'header_feature_dump,%s' % ','.join(_FeatureRow._fields)

  def to_csv(self):
    return ('"FEATURE_DUMP",%d,"%s","%s",'
            '%f' % (self.time, self.time_hr, self.product, self.kimchi))

  def to_log_string(self):
    return '\n' + self.to_csv()


class Pricer(object):
  def __init__(self, product_krw, product_usd):
    self._product_krw = product_krw
    self._product_usd = product_usd

    self._midp_krw = 0.
    self._midp_usd = 0.
    self._timestamp = 0

    self._counter = 0

  @property
  def ready(self):
    return self._midp_krw != 0 and self._midp_usd != 0

  def kimchi(self):
    if self.ready:
      return self._midp_krw / self._midp_usd
    else:
      return None

  def dumpable(self):
    if not self.ready:
      return
    self._counter += 1
    return self._counter % 20 == 0

  def update(self, side, book):
    if side == 0:  # 0: USD
      self._midp_usd = (book.ask0().price + book.bid0().price) / 2.
    else:
      self._midp_krw = (book.ask0().price + book.bid0().price) / 2.

    self._timestamp = book.timestamp

  def dump_feature(self):
    return FeatureRow(self._timestamp,
                      to_datetime(self._timestamp),
                      self._product_krw.base,
                      self.kimchi())


class KimchiSimStrategy(object):
  def __init__(self,
               product_pairs,
               pricing_param,
               executor_param,
               *,
               name=None,
               feature_filepath=None,
               fill_filepath=None):
    self._product_pairs = product_pairs

    self._start_ts = None

    self._books = {}
    self._pricers = {}

    for idx, products in enumerate(product_pairs):
      self._books[(idx, 0)] = None
      self._books[(idx, 1)] = None
      self._pricers[idx] = Pricer(products[0], products[1])

    self.name = name
    self._feature_filepath = feature_filepath
    self._feature_file = None
    self._last_feature_dump_ts = 0
    self._fill_filepath = fill_filepath
    self._fill_file = None

    self.fills = []

  @property
  def book_ready(self):
    return True
    return (self._books.get(1, None) is not None and self._books.get(2, None) is not None)

  def on_book_reset(self, book_builder_name, book_builder):
    for product_id, product_pair in enumerate(self._product_pairs):
      for side, product in enumerate(product_pair):
        subscription_key = get_subscription_key(product)
        if not book_builder_name == subscription_key:
          continue

        book_builder.subscribe(product, functools.partial(self.on_book, product_id, side))
        if hasattr(book_builder, "subscribe_trade"):
          book_builder.subscribe_trade(product, functools.partial(self.on_trade, product_id, side))

  def on_book(self, product_id, side, book):
    self._books[(product_id, side)] = book
    if not self.book_ready:
      return

    self._pricers[product_id].update(side, book)

    self._dump_feature(product_id)

  def on_trade(self, product_id, side, trade):
    pass

  def get_summary(self):
    pass

  def __del__(self):
    if self._feature_file is not None:
      self._feature_file.close()
    if self._fill_file is not None:
      self._fill_file.close()

  @property
  def _feature_dump_enabled(self):
    return self._feature_filepath is not None

  def _ensure_feature_file(self):
    if self._feature_dump_enabled and self._feature_file is None:
      self._feature_file = open(self._feature_filepath, 'w')
      self._feature_file.write(FeatureRow.get_csv_header() + '\n')

  def _dump_feature(self, product_id):
    if not self._feature_dump_enabled:
      return

    self._ensure_feature_file()
    if self._pricers[product_id].dumpable():
      self._feature_file.write(self._pricers[product_id].dump_feature().to_csv() + '\n')

  @property
  def _fill_dump_enabled(self):
    return self._fill_filepath is not None

  def _ensure_fill_file(self):
    if self._fill_dump_enabled and self._fill_file is None:
      self._fill_file = open(self._fill_filepath, 'w')
      self._fill_file.write(FillRow.get_csv_header() + '\n')

  def on_pos_update(self, exchange_id, pos, price, qty, side, maker, timestamp):
    side = 1 if side == OrderSide.BUY else -1
    row = FillRow(timestamp,
                  str(to_datetime(timestamp)),
                  exchange_id,
                  side,
                  price,
                  qty,
                  maker,
                  self.get_pos(1).pos,
                  self.get_pos(2).pos,
                  self.get_pos(1).pnl,
                  self.get_pos(2).pnl)
    self.fills.append(row)

    if not self._fill_dump_enabled:
      return

    self._ensure_fill_file()
    self._fill_file.write(row.to_csv() + '\n')
